/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University. All rights reserved.
 * 
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license; 2) All redistributions in binary form
 * must reproduce the above copyright notice, this list of conditions and the
 * disclaimer listed in this license in the documentation and/or other materials
 * provided with the distribution; 3) Any documentation included with all
 * redistributions must include the following acknowledgement:
 * 
 * "This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 * 
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 * 
 * 4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University. For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202. 5) Products
 * derived from this software may not be called NaradaBrokering, nor may Indiana
 * University or Community Grids Lab or NaradaBrokering appear in their name,
 * without prior written permission of ARTI.
 * 
 * 
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of any
 * other entity. Indiana University disclaims any liability to any recipient for
 * claims brought by any other entity based on infringement of intellectual
 * property rights or otherwise.
 * 
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 * GENERATED USING SOFTWARE.
 */
package cgl.narada.matching.tagvaluepairs;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;

import cgl.narada.event.NBEvent;
import cgl.narada.event.TemplateProfileAndSynopsisTypes;
import cgl.narada.matching.MatchingTree;
import cgl.narada.matching.Profile;
import cgl.narada.protocol.Destinations;
import cgl.narada.protocol.ProfilePropagationProtocol;

/**
 * This class implements the matching algorithm. This provides methods to add a
 * subscription and also to remove interest in a subscription predicate. The
 * matching algorithm should also be able to compute destinations based on the
 * general matching algorithm.
 * 
 * @author Shrideep Pallickara $Revision$ $Date$
 */

public class TagValueClientMatching implements MatchingTree,
    TagValueMatchingDebugFlags {
    /** This Hashtable maintains the list of different kinds of first tags */
    private Hashtable firstTags;

    /** Profile Propagation Handler */
    private ProfilePropagationProtocol profilePropagationProtocol;

    /** The unit level for this matching tree */
    private int unitLevel;

    /** The system level */
    private int systemLevel;

    /** The destination for this node */
    Destinations thisDestination;

    private Hashtable destinationsInfo;

    private Hashtable profileIds;

    private String moduleName = "TagValueClientMatching: ";

    /** The Matching constructor */
    public TagValueClientMatching(int unitLevel, int systemLevel,
        ProfilePropagationProtocol profilePropagationProtocol) {
        this.unitLevel = unitLevel;
        this.systemLevel = systemLevel;
        this.profilePropagationProtocol = profilePropagationProtocol;
        firstTags = new Hashtable();

        destinationsInfo = new Hashtable();
        profileIds = new Hashtable();
    }

    /**
     * This method returns the level assocaited with this matching tree. A node
     * could be a gateway at multiple levels. How it handles destinations at
     * different levels, and propagates destinations at different levels is
     * dicatated by the level of the matching tree which computes the
     * destinations and initiates profile propagation changes.
     * 
     * @return The level of the matching tree.
     */
    public int getMatchingLevel() {
        return unitLevel;
    }

    /** This method sets the destination address for this node */
    public void setNodeDestination(Destinations destination) {
        thisDestination = destination;
    }

    /**
     * This method adds a subscription predicate to the matching tree
     * 
     * @param profile
     *            The predicate comprising the subscription and the destination
     *            assocaited with the subscription.
     */
    public void addSubscriptionProfile(Profile profile) {
        TagValueProfile tagValueProfile = (TagValueProfile) profile;
        String profileId = tagValueProfile.getProfileId();
        if (profileId == null) {
            System.out.println(moduleName + "Profile Id is NULL in profile "
                + profile);
            return;
        }

        if (profileIds.containsKey(profileId)) {
            System.out.println(moduleName + "Profile " + profile
                + " has already been added ");
            return;
        }

        if (TagValueClientMatching_Debug) {
            System.out.println(moduleName + "Adding profile " + profile);
        }

        Vector tokenVector = processSubscription((String) tagValueProfile
            .getSubscription());

        if (tokenVector == null) {
            System.out.println(moduleName + "Error in subscription ["
                + tagValueProfile.getSubscription() + "]... returning");
            return;
        }

        int size = tokenVector.size();
        String firstTag = (String) tokenVector.firstElement();
        TagValueClientMatchingTreeNode tagNode;
        if (firstTags.containsKey(firstTag)) {
            tagNode = (TagValueClientMatchingTreeNode) firstTags.get(firstTag);
        } else {
            tagNode = new TagValueClientMatchingTreeNode(firstTag);
            firstTags.put(firstTag, tagNode);
        }
        int destination = tagValueProfile.getDestination();
        TagValueClientMatchingTreeEdge edge;
        TagValueClientMatchingTreeNode node;
        node = tagNode;
        for (int i = 1; i < size - 1;) {
            edge = tagNode.createEdgeLeadingIntoNode((String) tokenVector
                .elementAt(i++), (String) tokenVector.elementAt(i++),
                                                     destination, profile);
            tagNode = edge.getLeadingIntoNode();
        }
        boolean propagate = tagNode
            .createEdgeWithDestinations((String) tokenVector.lastElement(),
                                        destination, profile);

        if (profileId != null) {
            Integer destKey = new Integer(destination);
            if (!destinationsInfo.containsKey(destKey)) {
                destinationsInfo.put(destKey, new Hashtable());
            }

            ((Hashtable) destinationsInfo.get(destKey)).put(profileId, profile);
            profileIds.put(profileId, profile);

        } /* end (profileId!=null) */

        if (propagate) {
            if (TagValueClientMatching_Debug) {
                System.out.println("This predicate needs to be propagated ");
            }
            propagateChangesToHigherLevels(profile, true);
        }
    }

    public synchronized void removeProfilesForDestination(
                                                          Object clientIdentifier,
                                                          String linkId) {
        if (!destinationsInfo.containsKey(clientIdentifier)) {
            if (TagValueClientMatching_Debug) {
                System.out.println(moduleName + "Not aware of destination ["
                    + clientIdentifier
                    + "] submitted for removal from matching tree");
            }
            return;
        }

        Hashtable profileIdsToRemove = (Hashtable) destinationsInfo
            .get(clientIdentifier);
        Enumeration e = profileIdsToRemove.keys();
        while (e.hasMoreElements()) {
            Object _removeId = e.nextElement();
            Profile _profile = (Profile) profileIdsToRemove.remove(_removeId);
            removeSubscriptionProfile(_profile);
        }

    }

    /**
     * This method removes a subscription profile to the matching tree
     * 
     * @param profile
     *            The profile comprising the subscription and the destination
     *            assocaited with the subscription.
     */
    public synchronized void removeSubscriptionProfile(Profile profile) {
        TagValueProfile tagValueProfile = (TagValueProfile) profile;
        String profileId = tagValueProfile.getProfileId();

        if (profileId == null) {
            System.out.println(moduleName + "Profile Id is NULL in profile "
                + profile);
            return;
        }

        if (!profileIds.containsKey(profileId)) {
            System.out.println(moduleName + "Profile " + profile
                + " is not listed here");
            return;
        }

        propagateChangesToHigherLevels(profile, false);

        Vector tokenVector = processSubscription((String) tagValueProfile
            .getSubscription());
        if (tokenVector == null) {
            System.out.println("Error in subscription profile ["
                + tagValueProfile.getSubscription() + "]... returning");
            return;
        }

        int size = tokenVector.size();
        String firstTag = (String) tokenVector.firstElement();
        /**
         * Check to see if the first tag exists, if it doesn't we are trying to
         * remove a predicate that does not exist
         */
        if (!firstTags.containsKey(firstTag)) {
            System.out.println("Trying to remove a non-existant predicate ");
            return;
        }
        int destination = tagValueProfile.getDestination();
        TagValueClientMatchingTreeEdge edge;
        TagValueClientMatchingTreeNode node;
        node = (TagValueClientMatchingTreeNode) firstTags.get(firstTag);
        for (int i = 1; i < size - 1;) {
            edge = node.removeEdgeLeadingIntoNode((String) tokenVector
                .elementAt(i++), (String) tokenVector.elementAt(i++),
                                                  destination);
            if (edge == null) {
                System.out.println(moduleName + "removeSubPredicate::"
                    + "All edges and nodes from this point"
                    + " on have been discarded");

                /**
                 * This change needs to be propagated since it has resulted in
                 * the removal of an edge
                 */
                if (profileId != null) {
                    Object _clientIdentifier = new Integer(destination);
                    ((Hashtable) destinationsInfo.get(_clientIdentifier))
                        .remove(profileId);
                    if (((Hashtable) destinationsInfo.get(_clientIdentifier))
                        .size() == 0) {
                        destinationsInfo.remove(_clientIdentifier);
                        System.out.println(moduleName + "Destination ["
                            + _clientIdentifier
                            + "] removed from DestinationsInfo Table");
                    }
                    profileIds.remove(profileId);
                } /* if (profileId != null) */

                return;
            } /* end if (edge == null) */
            node = edge.getLeadingIntoNode();
        }

        boolean propagate = node
            .removeEdgeWithDestinations((String) tokenVector.lastElement(),
                                        destination);
        if (profileId != null) {
            Object _clientIdentifier = new Integer(destination);
            ((Hashtable) destinationsInfo.get(_clientIdentifier))
                .remove(profileId);
            if (((Hashtable) destinationsInfo.get(_clientIdentifier)).size() == 0) {
                destinationsInfo.remove(_clientIdentifier);
                System.out.println(moduleName + "Destination ["
                    + _clientIdentifier + "] has been removed "
                    + "from the DestinationsInfo Table");
            }
            profileIds.remove(profileId);

            System.out.println("Removed Profile ID" + profileId);
        } /* if (profileId != null) */

        // if (propagate) {
        // propagateChangesToHigherLevels(profile, false);
        // }

    }

    public void propagateChangesToHigherLevels(Profile profile,
                                               boolean addProfile) {
        if (profilePropagationProtocol == null) {
            return;
        }

        TagValueProfile tagvalueProfile = (TagValueProfile) profile;

        /**
         * The first step ofcourse is to replace lower level destinations with
         * the destination ID at the next higher level
         */
        if (TagValueClientMatching_Debug) {
            System.out
                .println("The destination associated with profile "
                    + profile.getSubscription() + " is "
                    + profile.getDestination());
        }
        int temp = 0;
        int tempDest;
        for (; temp <= systemLevel;) {
            tempDest = thisDestination.getDestinationsAtLevel(temp);
            if (TagValueClientMatching_Debug) {
                System.out.print("This destination would be replaced by "
                    + tempDest);
            }

            if (TagValueClientMatching_Debug) {
                System.out.println(" for representation in a level(" + temp
                    + ") tree ");
            }

            TagValueProfile _propagateProfile = new TagValueProfile(
                                                                    (String) tagvalueProfile
                                                                        .getSubscription(),
                                                                    tempDest,
                                                                    tagvalueProfile
                                                                        .getProfileId());
            profilePropagationProtocol
                .propagateProfileChange(_propagateProfile, temp, addProfile);
            temp++;
        }
    }

    /**
     * This method is responsible for processing a subscription, and first
     * splitting them up into individual tag value pairs followed by splitting
     * these tag-value pairs into individual tags and pairs. The subscription
     * MUST comprise of tag-value pairs else this method will throw an error
     */
    private Vector processSubscription(String subscription) {
        if (TagValueClientMatching_Debug)
            System.out.println(moduleName + "Processing subscription "
                + subscription);
        Vector tokenVector = new Vector();
        StringTokenizer tok = new StringTokenizer(subscription, ",");
        int i = 0;
        String tag;
        String value;
        while (tok.hasMoreTokens()) {
            i++;
            String tagValuePair = tok.nextToken();
            if (TagValueClientMatching_Debug)
                System.out.println("TagValue Pair " + i + " ->" + tagValuePair);
            StringTokenizer tok2 = new StringTokenizer(tagValuePair, "=");
            if (tok2.countTokens() == 2) {
                tag = tok2.nextToken().trim();
                value = tok2.nextToken().trim();
                if (TagValueClientMatching_Debug)
                    System.out.println("Tag = " + tag + " Value = " + value);
                tokenVector.addElement(tag);
                tokenVector.addElement(value);
            } else {
                System.out
                    .println("Malformed TAG=VALUE Pair in predicate with "
                        + "subscription " + subscription);
                return null;
            }
        }
        return tokenVector;
    }

    /**
     * This method return the destinations assocaited with an event that needs
     * to be matched to the matching tree
     * 
     * @return The destination as an INT
     */
    public Object matchEvent(NBEvent nbEvent) {
        TagValueEvent tvEvent = new TagValueEvent((String) nbEvent
            .getContentSynopsis());

        String tag = tvEvent.getNextTag();
        if (tag == null) {
            System.out.println(moduleName
                + "Event doesn't have any tag/value pairs");
            return null;
        }

        if (TagValueClientMatching_Debug) {
            System.out.println("Event to be matched has synopsis "
                + nbEvent.getContentSynopsis());
        }
        if (firstTags.containsKey(tag)) {
            TagValueClientMatchingTreeNode node = (TagValueClientMatchingTreeNode) firstTags
                .get(tag);
            return node.matchEvent(tvEvent, new Hashtable());
        }
        if (TagValueClientMatching_Debug)
            System.out.println(moduleName
                + "Event doesn't have any matching destinations");
        return null;
    }

    /** Returns the type of profiles managed by this matching engine */
    public int getManagedProfileType() {
        return TemplateProfileAndSynopsisTypes.TAG_VALUE_PAIRS;
    }

    public static void main(String[] args) {
    }
}
